/**
 * This code is part of the BLOOM project.
 * 
 * It is released under the Eclipse Public License (EPL).
 * 
 * 
 */ 
package logic {
	import flare.analytics.graph.BetweennessCentrality;
	import flare.data.DataSet;
	import flare.data.DataSource;
	import flare.display.TextSprite;
	import flare.flex.FlareVis;
	import flare.util.Strings;
	import flare.util.palette.SizePalette;
	import flare.vis.Visualization;
	import flare.vis.controls.AnchorControl;
	import flare.vis.controls.ClickControl;
	import flare.vis.controls.DragControl;
	import flare.vis.controls.HoverControl;
	import flare.vis.controls.PanZoomControl;
	import flare.vis.controls.TooltipControl;
	import flare.vis.data.Data;
	import flare.vis.data.EdgeSprite;
	import flare.vis.data.NodeSprite;
	import flare.vis.events.SelectionEvent;
	import flare.vis.events.TooltipEvent;
	import flare.vis.operator.OperatorList;
	import flare.vis.operator.encoder.SizeEncoder;
	import flare.vis.operator.label.Labeler;
	import flare.vis.operator.layout.Layout;
	import flare.vis.operator.layout.RadialTreeLayout;
	import flare.vis.operator.layout.CircleLayout;
	import flare.vis.operator.layout.CirclePackingLayout;
	import flare.vis.operator.layout.BundledEdgeRouter;
	import flare.vis.operator.layout.ForceDirectedLayout;
	import flare.vis.operator.layout.NodeLinkTreeLayout;
	import flare.vis.operator.layout.PieLayout;
	import flare.vis.operator.layout.DendrogramLayout;
	import flare.vis.operator.layout.IndentedTreeLayout;
	
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Rectangle;
	import flash.net.URLLoader;
	
	import mx.controls.*;
	import mx.core.*;
	
	public class EcosystemMap extends Sprite
	{
		// user interface
		private var container:FlareVis;
		private var profile:TextArea;

		// node-link diagram
		private var vis:Visualization;
		private var data:Data;
		private var center:NodeSprite;
		private var layout:Layout;
		public var CurEcoVisual:String="CircleLayout"; 

		/**
		 * Create an ecosystem map. On creation this loads initial data using loadData().
		 */
		public function EcosystemMap(container:FlareVis, url:String)
		{
			// using mx.core.Application.application.profile for compatibility (mw)
			profile = mx.core.Application.application.profile; 
			
			this.container = container;
         	vis = container.visualization;
         	// set boundaries to keep everything cleanly inside boundaries
			vis.bounds = new Rectangle(0, 0, container.width, container.height);
			container.scrollRect = vis.bounds;	// otherwise diagram will spill over
			vis.x = 0;
			vis.y = 0; 
			
			loadData(url);
		}
		
		/**
		 * Load ecosystem data from an url. Once the data is loaded, the ecosystem is visualized using visualize().
		 */
		public function loadData(url:String):void
        {
			var ds:DataSource = new DataSource(url, "graphml");
			var reduceWidthValue:int = 1;
			
			//Get reduce width value based on current node selected (url)
			reduceWidthValue = getReduceWidthValue(url);
				
            var loader:URLLoader = ds.load();
          	// wait for the data to load
            loader.addEventListener(Event.COMPLETE, function(evt:Event):void {
	            var ds:DataSet = loader.data as DataSet;
	            data = Data.fromDataSet(ds);
	            visualize(data, reduceWidthValue, CurEcoVisual);
	    	});		
        }
                       		
		/**
         * Visualize an ecosystem. Creates a layout, adds controls, tooltips, labels, and colors.
         * Also demonstrates capability of Flare to compute network metrics.
         */ 
        private function visualize(data:Data, reduceWidthValue:int, visual:String):void
        {
			// only do these things once
//			if (layout == null) { 
				addLayout(visual);  		
	       		addControls(); 
				addTooltips(); 	
//			} 

			// set data and create labels
			vis.data = data;
			vis.data.nodes["buttonMode"] = true;
			addLabels();			 
			resizeByCentrality();
			changeEdgeProperties(reduceWidthValue);								
			changeNodeProperties();
			vis.update();
        }
        
        /**
         * Create a layout.
         */ 
        private function addLayout(visual:String):void
        {
			// create the layout based on what visualization has been selected       
			
			switch (visual)  
			{ 
				case "CircleLayout": 
				{ 
					CurEcoVisual="CircleLayout"; 
					layout = new CircleLayout(); 
					vis.x = 0; 
					vis.y = 0;
					break; 
				} 
				case "CirclePackingLayout": 
				{ 
					CurEcoVisual="CirclePackingLayout"; 
					layout = new CirclePackingLayout(); 
					break; 
				} 
				case "ForceDirectedLayout": 
				{ 
					CurEcoVisual="ForceDirectedLayout"; 
					layout = new ForceDirectedLayout(); 
					vis.x = 0; 
					vis.y = 0;
					break; 
				} 
				case "NodeLinkTreeLayout": 
				{ 
					CurEcoVisual="NodeLinkTreeLayout"; 
					layout = new NodeLinkTreeLayout("leftToRight", 100); 
					vis.x = 0; 
					vis.y = 0;
					break; 
				} 
				case "RadialTreeLayout": 
				{ 
					CurEcoVisual="RadialTreeLayout"; 
					vis.x = 0; 
					vis.y = 0;
					layout = new RadialTreeLayout(); 
					break; 
				} 
				case "DendrogramLayout": 
				{ 
					CurEcoVisual="DendrogramLayout"; 
					layout = new DendrogramLayout(); 
					break; 
				}
				case "IndentedTreeLayout": 
				{ 
					CurEcoVisual="IndentedTreeLayout"; 
					layout = new IndentedTreeLayout(150); 
					vis.x = 100;
					vis.y = 20;
					break; 
				}
				default: 
				{ 
					CurEcoVisual="CircleLayout"; 
					layout = new CircleLayout(); 
					vis.x = 0; 
					vis.y = 0;
					break; 
				} 
			} 
			vis.operators.add(layout);
       }

		public function loadVisual(url:String, visual:String):void 
		{ 
			var reduceWidthValue:int = 1;
			
			//Get reduce width value based on current node selected (url)
			reduceWidthValue = getReduceWidthValue(url);
			var ds:DataSource = new DataSource(url, "graphml"); 
			var loader:URLLoader = ds.load(); 
			// wait for the data to load 
			loader.addEventListener(Event.COMPLETE, function(evt:Event):void { 
			var ds:DataSet = loader.data as DataSet; 
			data = Data.fromDataSet(ds); 
			
			//              visualize(data, visual); 
			}); 
			visualize(data, reduceWidthValue, visual); 
		} 
		
		/**
		 * Run the force-directed layout for a given number of iterations.
		 */ 
              
        /**
         * Add controls.
		 * <p>
         * ClickControl: captures node selection events
		 * <p>
         * HoverControl: called when user hovers over a node
         */
       	private function addControls():void
		{
			// 1 in the click control can be set to 2 to make it a double click
			vis.controls.add(new ClickControl(NodeSprite, 2, function (event:SelectionEvent):void
			{			
				if (center != event.node) {
					// display your favorite information about the node here
					// access custom node information: eg data.name, data.url, data.description, or
					// whatever other custom properties you defined in the graphml file
					var e:String = ""; // = "Selected node id: "+ event.node.data.id+ "\n";
					
					// build the e:string based on data that is available in the xml file
					if (event.node.data.name != null) {
						e = e + "<p><b>Name</b> "+ event.node.data.name + "</p>\n"; 
					}
//					if (event.node.data.type != null) {
//						e = e + "<p><b>Type</b> "+ event.node.data.type + "</p>\n"; 		
//					}
					if (event.node.data.description != null) {
						e = e + "<p><b>Description</b> "+ event.node.data.description + "</p>\n";
					}
					if (event.node.data.url != null) {
						e = e + "<p><b>URL</b> <a href='" + event.node.data.url + "' target='_blank'>" + 
							event.node.data.url + "</a></p>\n"; 	
					}
					if (event.node.data.address1 != null) {
						e = e + "<p><b>Address</b> "+ event.node.data.address1 + "</p>\n" ;
					}
					if (event.node.data.address2 != null) {
						e = e + "<p>" + event.node.data.address2 + "</p>\n" ;
					}
					if (event.node.data.city != null) {
						e = e + "<p><b>City</b> "+ event.node.data.city + "</p>\n" ;
					}
					if (event.node.data.postalcode != null) {
						e = e + "<p><b>Postal code</b> "+ event.node.data.postalcode + "</p>\n" ;
					}
					if (event.node.data.localemployees != null) {
						e = e + "<p><b>Local employees</b> "+ event.node.data.localemployees + "</p>\n" ;
					}
					if (event.node.data.yearfounded != null) {
						e = e + "<p><b>Year founded</b> "+ event.node.data.yearfounded + "</p>\n" ;
					}
					profile.htmlText = e;					
					center = event.node;
						
					// The next is a cludge for the time being it will reload data based on what node has been clicked
					var callfile:String = event.node.data.callfile;
					if (callfile != null) {
						if (callfile != "enter xml file here") {
							loadData("./database/" + callfile);
						}
					}
				}
			}));
					
			// TODO: in visualize() need to remove controls when loading a new data set
			// otherwise, visualizing will add multiple controls of the same time, and the
			// diagram will start to jitter as the controls are all applied together (mw)		
			
			vis.controls.add(new HoverControl(
				NodeSprite, HoverControl.MOVE_TO_FRONT));
			vis.controls.add(new DragControl(NodeSprite));

		}
		
		/**
		 * Show a tooltip as the user stops
		 * over a node or an edge.
		 */
		private function addTooltips():void
		{
			//Node tooltip
			vis.controls.add( new TooltipControl( NodeSprite, null, 
				function( evt:TooltipEvent ):void {
					//Display data.type value
					TextSprite( evt.tooltip ).htmlText = 
					Strings.format("{data.type}: {data.name}", evt.node); 
				}));
			
			//Edge tooltip
			vis.controls.add( new TooltipControl( EdgeSprite, null, 
				function( evt:TooltipEvent ):void {
					//Display data.width value
					TextSprite( evt.tooltip ).htmlText = 
					Strings.format("{data.width}", evt.edge); 
				}));
		}
				
		/**
		 * Add lables to all nodes.
		 */
		private function addLabels(min:int=1):void
		{
			var labeler:Labeler = new Labeler("data.name");
			labeler.textMode = TextSprite.DEVICE;
			vis.setOperator("labels", labeler);
			vis.update(null, "labels");
		}
		
		/**
		 * Change node properties based on content of graphml file.
		 * <p>
		 * data.x, data.y: fixed location of a node
		 */
		private function changeNodeProperties():void
        {
            data.nodes.setProperties({fillAlpha:1, lineAlpha:0});
			
			// visit all nodes
            vis.data.nodes.visit(function (node:NodeSprite):void
            {
            	if (node.data.x != undefined) {
                	node.x = node.data.x;
                	node.y = node.data.y;
             	}
             	if (node.data.fixed != undefined && node.data.fixed) {
             		node.fix();
             	}
             	setColor(node);
            });           
        }
        
        /**
         * Change edge properties.
         */ 
        private function changeEdgeProperties(reduceWidthValue:int):void
		{						
			// if an edge has a width key, use it, otherwise, set default width to 1
			vis.data.edges.visit(function (edge:EdgeSprite):void
			{				
				if(edge.data.width != undefined){
					edge.lineWidth = edge.data.width/reduceWidthValue;
				} else{
					edge.lineWidth = 1;
				}
				edge.fillColor = 4283546595;

			});
        }
        		
        /**
         * Change the color of a node based on its type.
		 * <p>
         * TODO: would be better to make this configurable
         */
		private function setColor(node:NodeSprite):void
        {
            var type:String = node.data.type;
             switch (type) 
            {
				 case "OCRI":
				 {
					 node.fillColor = 0xFFFF2400;	// orange red
					 break;
				 }
                case "Supplier":
                {
                    node.fillColor = 0xFFFF2400;	// orange red
                    break;
                }
				case "Customer":
				{
					node.fillColor = 0xff32CD32;	// green
					break;
				}
				case "Company":
				{
					node.fillColor = 0xff32CD32;	// green
					break;
				}
                case "Competitor":
                {
                    node.fillColor = 0xFF3232CD;	// medium blue
                    break;
                }
                case "Complementor":
                {
                    node.fillColor = 0xFFA68064;	// wood
                    break;
                }
                case "Contributor":
                {
                    node.fillColor = 0xFFDBDB70;	// goldenrod
                    break;
                }
				case "Role":
				{	
					node.fillColor = 0xFFE47833;	// mandarin orange
					break;
				}
				case "Cluster":
				{	
					node.fillColor = 0xFF3232CD;	// medium blue 

					break;
				}
				case "SubCluster":
				{	
					node.fillColor = 0xFFDBDB70;	// goldenrod
					break;
				}
               default:
                {
                    node.fillColor = 4283546595;
                    break;
                }
            }
        }
        
 		/**
 		 * Change size of nodes to their centrality.
 		 */
 		private function resizeByCentrality():void
		{
			// operators can be chained
			vis.setOperator("centrality", new OperatorList(
            	new BetweennessCentrality(),
             	new SizeEncoder("props.centrality", Data.NODES,
					new SizePalette(1, 5, true))
             	// uncomment to change color as well from cold to warm        	
             	//new ColorEncoder("props.centrality", Data.NODES, "fillColor", ScaleType.LOG, ColorPalette.hot());
            	
           ));			
			vis.update(null, "centrality").play();		
		}

      	/**
         * Save a model as an XML file. Generates output in GraphML.
		 * <p>
         * TODO: currently the XML file is just dumped to the text window, but it could 
         * be sent to the server instead using a web service
         */
        public function doSaveModel():void
        {
        	profile.htmlText = "&lt;graph edgedefault=\"undirected\"&gt;\n";
        	
        	profile.htmlText += "&lt;key id=\"name\" for=\"node\" attr.name=\"name\" attr.type=\"String\"/&gt;\n";
         	profile.htmlText += "&lt;key id=\"type\" for=\"node\" attr.name=\"type\" attr.type=\"String\"/&gt;\n";
          	profile.htmlText += "&lt;key id=\"x\" for=\"node\" attr.name=\"x\" attr.type=\"int\"/&gt;\n";
           	profile.htmlText += "&lt;key id=\"y\" for=\"node\" attr.name=\"y\" attr.type=\"int\"/&gt;\n";
 
 			// visit all nodes
        	vis.data.nodes.visit(function (node:NodeSprite):void
            {
            	var x:int = Math.round(node.x);
            	var y:int = Math.round(node.y);
            	var e:String = 
            		"&lt;node id=\"" + node.data.id + "\"&gt;\n" +
            		"  &lt;data key=\"name\"&gt;" + node.data.name + "&lt;/data&gt;\n" + 		
             		"  &lt;data key=\"type\"&gt;" + node.data.type + "&lt;/data&gt;\n" + 		
            		"  &lt;data key=\"x\"&gt;" + x + "&lt;/data&gt;\n" +
            		"  &lt;data key=\"y\"&gt;" + y + "&lt;/data&gt;\n" +
            		"&lt;/node&gt;\n";
                profile.htmlText += e;
            });       	
            
            // visit all edges
        	vis.data.edges.visit(function (edge:EdgeSprite):void
            {
				var e:String =
					"&lt;edge " +
					"source=\"" + edge.source.data.id + "\" " +
					"target=\"" + edge.target.data.id + 
					"\"/&gt;\n";
				profile.htmlText += e;
            });	       	
            profile.htmlText += "&lt;/graph&gt;\n";
        }
		
		/**
		 * Based on current node displayed, actual link widths changes.
		 * @return Link width reduction value based on node selected.
		 * <p>
		 * TODO:
		 * Currently values are hard coded for the demo.
		 * It would be better to make this dynamic based 
		 * on the maximum link width in the current display.
		 */ 
		private function getReduceWidthValue(url:String):int{			
			if(url == "./database/OCRI.xml" || url == "./database/clusters.xml"){
				//"OCRI" and "clusters" have large widths. Therefore, reduce each link by 20.
				return 20;				
			}else if(url == "./database/OCRIClea.xml"){
				//"OCRIClea" has medium widths. Therefore, reduce each link by 10.
				return 10;
			}else{
				//Others have small widths. Therefore, reduce each link by 3.
				return 5;
			}
		}
        	        
	}
}
